<?php

namespace app\middleware;

use app\exception\IndexException;
use support\Cache;
use Webman\Http\Response;
use Webman\Http\Request;
use Webman\MiddlewareInterface;

class Sign implements MiddlewareInterface
{
    protected $request;

    public function process(Request $request, callable $next): Response
    {
        $this->request = $request;
        $this->_checkSign();
        return $next($request);
    }

    /**
     * 执行签名认证
     * @return bool
     * @throws IndexException
     */
    private function _checkSign()
    {
        $notSignPath = config('param.notsignpath',[]);
        $path = ltrim(request()->path(),'\/');
        if(strpos($path,'test')===0 || strpos($path,'index/notify')===0 || in_array($path,$notSignPath)){
            return false;
        }

        if(strpos($path,'app/admin')===0 && (\request()->expectsJson()==false || strpos($path,'tool/index'))){
            return false;
        }

        // 检验是否要求开启签名认证,不同平台要求隔开
        $platform = $this->request->header('platform');
        if($platform==false && !in_array(strtoupper($platform),['IOS','ANDROID','ADMIN','WECHAT','WEB'])){
            throw new IndexException(1008,'请求身份错误');
        }

        if (!config('env.SIGN_'.strtoupper($platform).'_AUTH',true)){
            return false;
        }

        $headerData = [
            'uuid'=>$this->request->header('uuid'),
            'version'=>$this->request->header('version'),
            'platform'=>$this->request->header('platform'),
            'token'=>$this->request->header('token','empty'),
            'device'=>$this->request->header('device','empty'),
            'voucher'=>$this->request->header('voucher'),
            'time'=>$this->request->header('time'),
            'random'=>$this->request->header('random'),
        ];

        if($headerData['token']=='empty'){
            unset($headerData['token']);
        }
        if($headerData['device']=='empty'){
            unset($headerData['device']);
        }

        foreach($headerData as $key=>$val){
            if(in_array($key,['time','random','uuid','platform']) && $val==false){
                throw new IndexException(1008,'请求参数错误');
            }
            if($key=='voucher' && $val==false){
                throw new IndexException(1008,'请求签名验证错误');
            }
        }

        $paramData = $this->request->all();

        // 获取账号配置
        $signConfig = config('param.sign');

        // 缺少身份值
        if (!isset($signConfig[strtoupper($platform)])) {
            throw new IndexException(1008,'请求错误,缺少身份验证');
        }

        // 生成签名
        $sign = $this->getSign($headerData, $paramData, $signConfig[strtoupper($platform)]);

        // 验证签名
        if ($sign != $headerData['voucher']) {
            throw new IndexException(1008,'请求签名验证错误');
        }

        //签名通过后，对请求时间戳限制和频率限制,特殊接口不做校验
        $pathInfo = rtrim($this->request->path(),'/');
        if(in_array(strtoupper($platform),['WECHAT','IOS','ANDROID'])){
            $time = (int) (substr((string)$headerData['time'],0,10));
            $key = md5($headerData['voucher']);
            $requestNums = Cache::get($key) ?? 1;
            if($time+8<time()){
                //throw new IndexException(1008,'请求会话已过期');
            }

            if($requestNums && $requestNums>3){
                //throw new IndexException(1008,'请求会话已超出');
            }
            cache::set($key,$requestNums+1,8);
        }

        return true;
    }

    /**
     * 获取生成的签名字符串
     * @param array $headerData
     * @param array $paramData
     * @param string $encryKey
     * @return string
     */
    protected function getSign($headerData, $paramData, $encryKey)
    {
        // 不需要参与签名的键值
        if(strtoupper($headerData['platform'])=='ADMIN'){
            $noSignKey = ['voucher','file', 's', 'page', 'limit','field','order','tailor','__file__'];
        }else{
            $noSignKey = ['voucher','file'];
        }

        $str = '';
        ksort($headerData);
        foreach ($headerData as $key => $val) {
            if (in_array($key, $noSignKey)) {
                continue;
            }

            $val = htmlspecialchars_decode($val);
            if ($val === '' || is_null($val)) {
                $str .= $key.'=false{{ANYUE}}';
            }else{
                $str .= $key.'='.$val.'{{ANYUE}}';
            }
        }

        $string1 =  md5(md5($str).md5($encryKey));;

        $str = '';
        ksort($paramData);
        foreach ($paramData as $key => $val) {
            if (in_array($key, $noSignKey)) {
                continue;
            }

            if(is_array($val)){
                $isString = false;
                foreach($val as $k=>$v){
                    if(is_string($k)){
                        $isString = true;
                    }
                }
                if($isString){
                    $str .= $key.'=object{{ANYUE}}';
                }else{
                    $str .= $key.'=array{{ANYUE}}';
                }
            }else{
                if ($val === '' || is_null($val)) {
                    $str .= $key.'=false{{ANYUE}}';
                }else{
                    $str .= $key.'='.$val.'{{ANYUE}}';
                }
            }
        }

        $string2 =  md5(md5($str).md5($encryKey));
        return  $string1.$string2;
    }


}
